package actor

const (
	maxErrorCount int = 100 // TODO 发生多少次错误后停止actor
)

type localContext struct {
	actor          IActor
	self           *PID
	parent         *PID
	children       PIDSet
	watchers       PIDSet
	watching       PIDSet
	stopping       bool
	errorCount     int
	supervisorFunc SupervisorFunc
}

func newLocalContext(actor IActor, self *PID, parent *PID, supervisorFunc SupervisorFunc) *localContext {
	ctx := &localContext{
		actor:          actor,
		self:           self,
		parent:         parent,
		supervisorFunc: supervisorFunc,
	}
	return ctx
}

func (ctx *localContext) processMessage(message interface{}, sender *PID) (continued bool) {
	defer func() {
		if r := recover(); r != nil {
			ctx.processError(r)
		}
	}()

	continued = true

	switch msg := message.(type) {
	case *Started:
		ctx.actor.OnReceiveMessage(message, sender, ctx)

	case *Stop:
		ctx.stopping = true
		// 停止子actor
		ctx.children.Each(func(_ string, p PID) {
			p.ref().SendMessage(stopMessage, ctx.self)
		})
		ctx.self.ref().SendMessage(stoppingMessage, ctx.self)

	case *Stopping:
		ctx.actor.OnReceiveMessage(message, sender, ctx)
		ctx.self.ref().SendMessage(stoppedMessage, ctx.self)

	case *Stopped:
		ctx.stopping = false
		continued = false
		ctx.actor.OnReceiveMessage(message, sender, ctx)
		// 通知watcher actor停止
		terminated := &Terminated{Who: ctx.self}
		ctx.watchers.Each(func(_ string, p PID) {
			p.ref().SendMessage(terminated, ctx.self)
		})

	case *Terminated:
		ctx.actor.OnReceiveMessage(message, sender, ctx)
		ctx.children.Remove(msg.Who)
		ctx.watching.Remove(msg.Who)

	case *Restart:
		ctx.self.ref().SendMessage(restartingMessage, ctx.self)

	case *Restarting:
		ctx.actor.OnReceiveMessage(message, sender, ctx)
		ctx.self.ref().SendMessage(restartedMessage, ctx.self)

	case *Restarted:
		ctx.actor.OnReceiveMessage(message, sender, ctx)

	case *Watch:
		if ctx.stopping {
			msg.Watcher.sendMessage(&Terminated{Who: ctx.self}, ctx.self)
		} else {
			ctx.watchers.Add(msg.Watcher)
		}

	case *Unwatch:
		ctx.watchers.Remove(msg.Watcher)

	default:
		ctx.actor.OnReceiveMessage(message, sender, ctx)
	}

	return
}

func (ctx *localContext) processError(reason interface{}) {
	ctx.errorCount++

	switch ctx.supervisorFunc(reason, ctx) {
	case ResumeDirective:
		// 什么都不做

		if ctx.errorCount >= maxErrorCount {
			// TODO 发生错误超过上限, 停止actor
			ctx.self.ref().SendMessage(stopMessage, ctx.self)
		}

	case RestartDirective:
		if ctx.errorCount >= maxErrorCount {
			// TODO 发生错误超过上限, 停止actor
			ctx.self.ref().SendMessage(stopMessage, ctx.self)
		} else {
			ctx.self.ref().SendMessage(restartMessage, ctx.self)
		}

	case StopDirective:
		ctx.self.ref().SendMessage(stopMessage, ctx.self)

	case EscalateDirective:
		//if ctx.parent != nil {
		// TODO 通知父actor处理错误
		//} else {
		panic(reason)
		//}
	}

	return
}

func (ctx *localContext) Actor() IActor {
	return ctx.actor
}

func (ctx *localContext) Self() *PID {
	return ctx.self
}

func (ctx *localContext) Parent() *PID {
	return ctx.parent
}

func (ctx *localContext) Tell(pid *PID, message interface{}) {
	pid.ref().SendMessage(message, ctx.self)
}

func (ctx *localContext) Request(pid *PID, message interface{}) {
	// TODO pid.ref().SendMessage(message, ctx.self)
}

func (ctx *localContext) Children() map[string]*PID {
	r := make(map[string]*PID)
	ctx.children.Each(func(s string, p PID) {
		r[s] = &p
	})
	return r
}

func (ctx *localContext) Watch(who *PID) {
	who.sendMessage(&Watch{Watcher: ctx.self}, ctx.self)
	ctx.watching.Add(who)
}

func (ctx *localContext) Unwatch(who *PID) {
	who.sendMessage(&Unwatch{Watcher: ctx.self}, ctx.self)
	ctx.watching.Remove(who)
}

func (ctx *localContext) Spawn(props *Props) *PID {
	pid, _ := ctx.SpawnNamed(props, gProcessManager.NextName())
	return pid
}

func (ctx *localContext) SpawnPrefix(props *Props, prefix string) *PID {
	pid, _ := ctx.SpawnNamed(props, prefix+gProcessManager.NextName())
	return pid
}

func (ctx *localContext) SpawnNamed(props *Props, name string) (*PID, error) {
	pid, err := spawn(ctx.self.Name+"/"+name, props, ctx.self)
	if err != nil {
		return pid, err
	}

	ctx.children.Add(pid)
	ctx.Watch(pid)

	return pid, nil
}

func (ctx *localContext) String() string {
	return ctx.self.String()
}
